#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <set>

#include <getopt.h>

using namespace std;

namespace {
	bool opt_delete = false;
	bool opt_read = false;
	bool opt_new = false;
	bool opt_outbox = false;
	bool opt_sent = false;
	bool opt_watch = false;
}

void help(const char * app) {
	cout << "Help:" << endl;
	cout << '\t' << "Usage:" << endl;
	cout << '\t' << '\t' << app << " [options] device" << endl;
	cout << '\t' << "Options:" << endl;
	cout << '\t' << '\t' << "-d, --delete   Delete the messages from the phone after reading." << endl;
	cout << '\t' << '\t' << "-r, --read     Read messages that have been marked as 'read'." << endl;
	cout << '\t' << '\t' << "-n, --new      Read messages that have been marked as 'new'." << endl;
	cout << '\t' << '\t' << "-o, --outbox   Read messages that are waiting to be sent." << endl;
	cout << '\t' << '\t' << "-s, --sent     Read messages that have been sent in the past." << endl;
	cout << '\t' << '\t' << "-i, --inbox    Read all incoming messages. Equivalent to combining -r and -n." << endl;
	cout << '\t' << '\t' << "-a, --all      Read all messages. Equivalent to combining -r, -n, -o and -s." << endl;
	cout << '\t' << '\t' << "               This is the default behaviour when no of the filter options is used." << endl;
	cout << '\t' << '\t' << "-w, --watch    Continuously read messages." << endl;
	cout << '\t' << "Examples:" << endl;
	cout << '\t' << '\t' << app << " /dev/rfcomm0" << endl;
	cout << '\t' << '\t' << app << " -nwd /dev/ttyS0 | smsdecode" << endl;
	cout << endl;
	cout << "Please report any bugs or suggestions to: Maurice Bos <m-ou.se@m-ou.se>" << endl;
	cout << endl;
}

bool readExpected(istream & in, char const * data) {
	while(char c = *data++) if (in.get() != c) return false;
	return true;
}

bool send(istream & in, ostream & out, char const * data) {
	while(char c = *data++){
		in.sync();
		out.put(c);
		out.flush();
		if (in.get() != c) return false;
	}
	return true;
}

bool readHexDigit(istream & in, unsigned char & digit) {
	char c = in.get();
	if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')){ digit = c >= 'A' ? c-'A'+10 : c-'0'; return true; }
	return false;
}

bool readHexByte(istream & in, unsigned char & byte) {
	unsigned char h, l;
	if (!readHexDigit(in,h) || !readHexDigit(in,l)) return false;
	byte = (h << 4) + l;
	return true;
}

void putHexDigit(ostream & out, unsigned char digit) {
	out.put(digit < 10 ? digit + '0' : digit - 10 + 'A');
}

void putHexByte(ostream & out, unsigned char byte) {
	putHexDigit(out,byte >> 4);
	putHexDigit(out,byte & 0xF);
}

void listMessages(istream & in, ostream & out, char const * list){
	if (!send(in,out,list)) throw "Unable to request message list.";
	if (!readExpected(in,"\r\n")) throw "Unable to get message list.";
	
	set<unsigned int> messages_to_delete;
	
	while(in.peek() == '+'){
		
		if (!readExpected(in,"+CMGL: ")) throw "Invalid message list.";
		
		unsigned int index;
		if (!(in >> index)) throw "Invalid index in message list.";
		
		if (!readExpected(in,",")) throw "Invalid data after index in message list.";
		
		unsigned int status;
		if (!(in >> status)) throw "Invalid status in message list.";
		
		if (!readExpected(in,",,")) throw "Invalid data after status in message list.";
		
		size_t tpdu_size;
		if (!(in >> tpdu_size)) throw "Invalid TPDU size in message list.";
		
		if (!readExpected(in,"\r\n")) throw "Invalid data after TPDU size in message list.";
		
		unsigned char smsc_size_header;
		if (!readHexByte(in,smsc_size_header)) throw "Invalid SMSC size in message data.";
		
		size_t smsc_size = size_t(smsc_size_header)+1;
		unsigned char smsc[smsc_size];
		smsc[0] = smsc_size_header;
		for(size_t i = 1; i < smsc_size; i++) if (!readHexByte(in,smsc[i])) throw "Invalid SMSC data in message.";
		
		unsigned char tpdu[tpdu_size];
		for(size_t i = 0; i < tpdu_size; i++) if (!readHexByte(in,tpdu[i])) throw "Invalid TPDU data in message.";
		
		if (!readExpected(in,"\r\n")) throw "Invalid data after message data.";
		
		cout << index << ' ' << status << ' ' << (smsc_size + tpdu_size) << ' ';
		for(size_t i = 0; i < smsc_size; i++) putHexByte(cout,smsc[i]);
		for(size_t i = 0; i < tpdu_size; i++) putHexByte(cout,tpdu[i]);
		cout << endl;
		
		if (opt_delete) messages_to_delete.insert(index);
	}
	
	if (!readExpected(in,"\r\nOK\r\n")) throw "Error while getting message list.";
	
	if (opt_delete) for(set<unsigned int>::iterator i = messages_to_delete.begin(); i != messages_to_delete.end(); i++){
		stringstream index_str;
		index_str << *i;
		if (!send(in,out,"AT+CMGD=") || !send(in,out,index_str.str().c_str()) || !send(in,out,"\r")) throw "Unable to request deletion of message.";
		if (!readExpected(in,"\r\nOK\r\n")) throw "Unable to delete message.";
	}
}

int main(int argc, char * * argv) {
	
	if (argc < 2 || (argc == 2 && (argv[1] == string("-?") || argv[1] == string("-h") || argv[1] == string("--help")))){
		help(argv[0]);
		return 0;
	}
	
	char const * device = argv[argc-1];
	
	struct option const long_options[] = {
		{ "delete", no_argument, 0, 'd' },
		{ "read"  , no_argument, 0, 'r' },
		{ "new"   , no_argument, 0, 'n' },
		{ "outbox", no_argument, 0, 'o' },
		{ "sent"  , no_argument, 0, 's' },
		{ "inbox" , no_argument, 0, 'i' },
		{ "all"   , no_argument, 0, 'a' },
		{ "watch" , no_argument, 0, 'w' },
		{ 0, 0, 0, 0 }
	};

	while(true){
		int option = getopt_long(argc - 1, argv, "drnosiaw", long_options, 0);
		if (option < 0) break;
		switch(option){
			case 'd': opt_delete = true; break;
			case 'r': opt_read   = true; break;
			case 'n': opt_new    = true; break;
			case 'o': opt_outbox = true; break;
			case 's': opt_sent   = true; break;
			case 'w': opt_watch  = true; break;
			case 'i':
				opt_read   = true;
				opt_new    = true;
				break;
			case 'a':
				opt_read   = true;
				opt_new    = true;
				opt_outbox = true;
				opt_sent   = true;
				break;
		}
	}
	
	if (opt_read == false && opt_new == false && opt_outbox == false && opt_sent == false){
		opt_read   = true;
		opt_new    = true;
		opt_outbox = true;
		opt_sent   = true;
	}
	
	ifstream  in(device, ios::binary);
	ofstream out(device, ios::binary);
	
	if (!in.good() || !out.good()){
		cerr << "Error: Unable to open '" << device << "'." << endl;
		return -1;
	}
	
	try{
		if (!send(in,out,"AT\r")) throw "Unable to request attention.";
		if (!readExpected(in,"\r\nOK\r\n")) throw "Unable to get attention.";
		
		do{
			
			if (opt_read && opt_new && opt_outbox && opt_sent){
				listMessages(in,out,"AT+CMGL=4\r");
			} else {
				if (opt_new)    listMessages(in,out,"AT+CMGL=0\r");
				if (opt_read)   listMessages(in,out,"AT+CMGL=1\r");
				if (opt_outbox) listMessages(in,out,"AT+CMGL=2\r");
				if (opt_sent)   listMessages(in,out,"AT+CMGL=3\r");
			}
			
			if (opt_watch && !(opt_delete || (opt_new && !opt_read && !opt_outbox && !opt_sent))) cout << endl;
			
		} while(opt_watch);
		
	}catch(char const * error){
		cerr << "Error: " << error << endl;
		return -1;
	}
	
	return 0;
}

